/*-------------------------------------------------------------------------
 *
 * localbuf.c
 *	  本地缓冲区管理器。 用于临时表的快速缓冲区管理器，
 *	  永远不需要进行 WAL 记录或检查点等。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/storage/buffer/localbuf.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/parallel.h"
#include "catalog/catalog.h"
#include "executor/instrument.h"
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/resowner_private.h"


/*#define LBDEBUG*/

/* 缓冲查找哈希表的条目 */
typedef struct
{
	BufferTag	key;			/* 磁盘页的标签 */
	int			id;				/* 关联本地缓冲区的索引 */
} LocalBufferLookupEnt;

/* 注意：此宏仅适用于本地缓冲区，而不适用于共享缓冲区！ */
#define LocalBufHdrGetBlock(bufHdr) \
	LocalBufferBlockPointers[-((bufHdr)->buf_id + 2)]

int			NLocBuffer = 0;		/* 直到缓冲区被初始化 */

BufferDesc *LocalBufferDescriptors = NULL;
Block	   *LocalBufferBlockPointers = NULL;
int32	   *LocalRefCount = NULL;

static int	nextFreeLocalBuf = 0;

static HTAB *LocalBufHash = NULL;


static void fc_InitLocalBuffers(void);
static Block fc_GetLocalBufferStorage(void);


/*
 * PrefetchLocalBuffer -
 *	  启动对关系块的异步读取
 *
 * 对临时关系执行 PrefetchBuffer 的工作。
 * 如果未编译预取功能，则无操作。
 */
PrefetchBufferResult PrefetchLocalBuffer(SMgrRelation fc_smgr, ForkNumber fc_forkNum,
					BlockNumber fc_blockNum)
{
	PrefetchBufferResult fc_result = {InvalidBuffer, false};
	BufferTag	fc_newTag;			/* 请求块的标识 */
	LocalBufferLookupEnt *fc_hresult;

	INIT_BUFFERTAG(fc_newTag, fc_smgr->smgr_rnode.node, fc_forkNum, fc_blockNum);

	/* 如果这是本会话中的第一次请求，则初始化本地缓冲区 */
	if (LocalBufHash == NULL)
		fc_InitLocalBuffers();

	/* 查看所需的缓冲区是否已存在 */
	fc_hresult = (LocalBufferLookupEnt *)
		hash_search(LocalBufHash, (void *) &fc_newTag, HASH_FIND, NULL);

	if (fc_hresult)
	{
		/* 是的，所以没有需要做的 */
		fc_result.recent_buffer = -fc_hresult->id - 1;
	}
	else
	{
#ifdef USE_PREFETCH
		/* 不在缓冲区中，因此启动预取 */
		smgrprefetch(fc_smgr, fc_forkNum, fc_blockNum);
		fc_result.initiated_io = true;
#endif							/* USE_PREFETCH */
	}

	return fc_result;
}


/*
 * LocalBufferAlloc -
 *	  查找或创建给定关系的给定页面的本地缓冲区。
 *
 * API 与 bufmgr.c 的 BufferAlloc 类似，只是我们不需要
 * 进行任何锁定，因为这一切都是本地的。此外，IO_IN_PROGRESS
 * 不会被设置。最后，我们仅支持默认访问策略
 * （因此，usage_count 始终会增加）。
 */
BufferDesc *
LocalBufferAlloc(SMgrRelation fc_smgr, ForkNumber fc_forkNum, BlockNumber fc_blockNum,
				 bool *fc_foundPtr)
{
	BufferTag	fc_newTag;			/* 请求块的标识 */
	LocalBufferLookupEnt *fc_hresult;
	BufferDesc *fc_bufHdr;
	int			fc_b;
	int			fc_trycounter;
	bool		fc_found;
	uint32		fc_buf_state;

	INIT_BUFFERTAG(fc_newTag, fc_smgr->smgr_rnode.node, fc_forkNum, fc_blockNum);

	/* 如果这是本会话中的第一次请求，则初始化本地缓冲区 */
	if (LocalBufHash == NULL)
		fc_InitLocalBuffers();

	/* 查看所需的缓冲区是否已存在 */
	fc_hresult = (LocalBufferLookupEnt *)
		hash_search(LocalBufHash, (void *) &fc_newTag, HASH_FIND, NULL);

	if (fc_hresult)
	{
		fc_b = fc_hresult->id;
		fc_bufHdr = GetLocalBufferDescriptor(fc_b);
		Assert(BUFFERTAGS_EQUAL(fc_bufHdr->tag, fc_newTag));
#ifdef LBDEBUG
		fprintf(stderr, "LB ALLOC (%u,%d,%d) %d\n",
				fc_smgr->smgr_rnode.node.relNode, fc_forkNum, fc_blockNum, -fc_b - 1);
#endif
		fc_buf_state = pg_atomic_read_u32(&fc_bufHdr->state);

		/* 该部分相当于对共享缓冲区进行 PinBuffer */
		if (LocalRefCount[fc_b] == 0)
		{
			if (BUF_STATE_GET_USAGECOUNT(fc_buf_state) < BM_MAX_USAGE_COUNT)
			{
				fc_buf_state += BUF_USAGECOUNT_ONE;
				pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);
			}
		}
		LocalRefCount[fc_b]++;
		ResourceOwnerRememberBuffer(CurrentResourceOwner,
									BufferDescriptorGetBuffer(fc_bufHdr));
		if (fc_buf_state & BM_VALID)
			*fc_foundPtr = true;
		else
		{
			/* 先前的读取尝试必须已失败；再试一次 */
			*fc_foundPtr = false;
		}
		return fc_bufHdr;
	}

#ifdef LBDEBUG
	fprintf(stderr, "LB ALLOC (%u,%d,%d) %d\n",
			fc_smgr->smgr_rnode.node.relNode, fc_forkNum, fc_blockNum,
			-nextFreeLocalBuf - 1);
#endif

	/*
	 * 需要获取一个新缓冲区。我们使用时钟清扫算法（实际上
	 * 与 freelist.c 现在所做的相同...）
	 */
	fc_trycounter = NLocBuffer;
	for (;;)
	{
		fc_b = nextFreeLocalBuf;

		if (++nextFreeLocalBuf >= NLocBuffer)
			nextFreeLocalBuf = 0;

		fc_bufHdr = GetLocalBufferDescriptor(fc_b);

		if (LocalRefCount[fc_b] == 0)
		{
			fc_buf_state = pg_atomic_read_u32(&fc_bufHdr->state);

			if (BUF_STATE_GET_USAGECOUNT(fc_buf_state) > 0)
			{
				fc_buf_state -= BUF_USAGECOUNT_ONE;
				pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);
				fc_trycounter = NLocBuffer;
			}
			else
			{
				/* 找到了可用的缓冲区 */
				LocalRefCount[fc_b]++;
				ResourceOwnerRememberBuffer(CurrentResourceOwner,
											BufferDescriptorGetBuffer(fc_bufHdr));
				break;
			}
		}
		else if (--fc_trycounter == 0)
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
					 errmsg("no empty local buffer available")));
	}

	/*
	 * 这个缓冲区没有被引用，但它可能仍然是脏的。如果是这样，
	 * 在重新使用之前将其写出！
	 */
	if (fc_buf_state & BM_DIRTY)
	{
		SMgrRelation fc_oreln;
		Page		fc_localpage = (char *) LocalBufHdrGetBlock(fc_bufHdr);

		/* 查找缓冲区的smgr关系 */
		fc_oreln = smgropen(fc_bufHdr->tag.rnode, MyBackendId);

		PageSetChecksumInplace(fc_localpage, fc_bufHdr->tag.blockNum);

		/* 并写... */
		smgrwrite(fc_oreln,
				  fc_bufHdr->tag.forkNum,
				  fc_bufHdr->tag.blockNum,
				  fc_localpage,
				  false);

		/* 现在标记为不脏，以防下面出现错误 */
		fc_buf_state &= ~BM_DIRTY;
		pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);

		pgBufferUsage.local_blks_written++;
	}

	/* 
	 * 懒惰内存分配：在第一次使用缓冲区时分配空间。
	 */
	if (LocalBufHdrGetBlock(fc_bufHdr) == NULL)
	{
		/* 为 BufferGetBlock() 宏设置指针 */
		LocalBufHdrGetBlock(fc_bufHdr) = fc_GetLocalBufferStorage();
	}

	/* 
	 * 更新哈希表：如果有旧条目，则删除它，并创建新的条目。
	 */
	if (fc_buf_state & BM_TAG_VALID)
	{
		fc_hresult = (LocalBufferLookupEnt *)
			hash_search(LocalBufHash, (void *) &fc_bufHdr->tag,
						HASH_REMOVE, NULL);
		if (!fc_hresult)			/* 不应该发生 */
			elog(ERROR, "local buffer hash table corrupted");
		/* 确保缓冲区无效，以防哈希插入失败 */
		CLEAR_BUFFERTAG(fc_bufHdr->tag);
		fc_buf_state &= ~(BM_VALID | BM_TAG_VALID);
		pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);
	}

	fc_hresult = (LocalBufferLookupEnt *)
		hash_search(LocalBufHash, (void *) &fc_newTag, HASH_ENTER, &fc_found);
	if (fc_found)					/* 不应该发生 */
		elog(ERROR, "local buffer hash table corrupted");
	fc_hresult->id = fc_b;

	/* 
	 * 现在一切都是我们的了。
	 */
	fc_bufHdr->tag = fc_newTag;
	fc_buf_state &= ~(BM_VALID | BM_DIRTY | BM_JUST_DIRTIED | BM_IO_ERROR);
	fc_buf_state |= BM_TAG_VALID;
	fc_buf_state &= ~BUF_USAGECOUNT_MASK;
	fc_buf_state += BUF_USAGECOUNT_ONE;
	pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);

	*fc_foundPtr = false;
	return fc_bufHdr;
}

/* 
 * MarkLocalBufferDirty -
 *	  标记一个本地缓冲区为脏
 */
void MarkLocalBufferDirty(Buffer fc_buffer)
{
	int			fc_bufid;
	BufferDesc *fc_bufHdr;
	uint32		fc_buf_state;

	Assert(BufferIsLocal(fc_buffer));

#ifdef LBDEBUG
	fprintf(stderr, "LB DIRTY %d\n", fc_buffer);
#endif

	fc_bufid = -(fc_buffer + 1);

	Assert(LocalRefCount[fc_bufid] > 0);

	fc_bufHdr = GetLocalBufferDescriptor(fc_bufid);

	fc_buf_state = pg_atomic_read_u32(&fc_bufHdr->state);

	if (!(fc_buf_state & BM_DIRTY))
		pgBufferUsage.local_blks_dirtied++;

	fc_buf_state |= BM_DIRTY;

	pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);
}

/* 
 * DropRelFileNodeLocalBuffers
 *		该函数从缓冲池中移除指定关系的所有页，其块编号 >= firstDelBlock。
 *		（特别是，当 firstDelBlock = 0 时，所有页面都会被移除。）
 *		脏页会简单地被丢弃，而不先写出它们。因此，这不是可回滚的，所以应该
 *		仅在极其谨慎的情况下使用！
 *
 *		有关更多注释，请参见 bufmgr.c 中的 DropRelFileNodeBuffers。
 */
void DropRelFileNodeLocalBuffers(RelFileNode fc_rnode, ForkNumber fc_forkNum,
							BlockNumber fc_firstDelBlock)
{
	int			fc_i;

	for (fc_i = 0; fc_i < NLocBuffer; fc_i++)
	{
		BufferDesc *fc_bufHdr = GetLocalBufferDescriptor(fc_i);
		LocalBufferLookupEnt *fc_hresult;
		uint32		fc_buf_state;

		fc_buf_state = pg_atomic_read_u32(&fc_bufHdr->state);

		if ((fc_buf_state & BM_TAG_VALID) &&
			RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_rnode) &&
			fc_bufHdr->tag.forkNum == fc_forkNum &&
			fc_bufHdr->tag.blockNum >= fc_firstDelBlock)
		{
			if (LocalRefCount[fc_i] != 0)
				elog(ERROR, "block %u of %s is still referenced (local %u)",
					 fc_bufHdr->tag.blockNum,
					 relpathbackend(fc_bufHdr->tag.rnode, MyBackendId,
									fc_bufHdr->tag.forkNum),
					 LocalRefCount[fc_i]);
			/* 从哈希表中移除条目 */
			fc_hresult = (LocalBufferLookupEnt *)
				hash_search(LocalBufHash, (void *) &fc_bufHdr->tag,
							HASH_REMOVE, NULL);
			if (!fc_hresult)		/* 不应该发生 */
				elog(ERROR, "local buffer hash table corrupted");
			/* 标记缓冲区无效 */
			CLEAR_BUFFERTAG(fc_bufHdr->tag);
			fc_buf_state &= ~BUF_FLAG_MASK;
			fc_buf_state &= ~BUF_USAGECOUNT_MASK;
			pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);
		}
	}
}

/* 
 * DropRelFileNodeAllLocalBuffers
 *		该函数从缓冲池中移除指定关系的所有分叉的所有页面。
 *
 *		有关更多注释，请参见 bufmgr.c 中的 DropRelFileNodesAllBuffers。
 */
void DropRelFileNodeAllLocalBuffers(RelFileNode fc_rnode)
{
	int			fc_i;

	for (fc_i = 0; fc_i < NLocBuffer; fc_i++)
	{
		BufferDesc *fc_bufHdr = GetLocalBufferDescriptor(fc_i);
		LocalBufferLookupEnt *fc_hresult;
		uint32		fc_buf_state;

		fc_buf_state = pg_atomic_read_u32(&fc_bufHdr->state);

		if ((fc_buf_state & BM_TAG_VALID) &&
			RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_rnode))
		{
			if (LocalRefCount[fc_i] != 0)
				elog(ERROR, "block %u of %s is still referenced (local %u)",
					 fc_bufHdr->tag.blockNum,
					 relpathbackend(fc_bufHdr->tag.rnode, MyBackendId,
									fc_bufHdr->tag.forkNum),
					 LocalRefCount[fc_i]);
			/* 从哈希表中移除条目 */
			fc_hresult = (LocalBufferLookupEnt *)
				hash_search(LocalBufHash, (void *) &fc_bufHdr->tag,
							HASH_REMOVE, NULL);
			if (!fc_hresult)		/* 不应该发生 */
				elog(ERROR, "local buffer hash table corrupted");
			/* 标记缓冲区无效 */
			CLEAR_BUFFERTAG(fc_bufHdr->tag);
			fc_buf_state &= ~BUF_FLAG_MASK;
			fc_buf_state &= ~BUF_USAGECOUNT_MASK;
			pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);
		}
	}
}

/* 
 * InitLocalBuffers -
 *	  初始化本地缓冲区缓存。由于大多数查询（尤其是多用户查询）
 *	  不涉及本地缓冲区，因此我们延迟真正分配缓冲区所需的内存；
 *	  这里只需创建缓冲区头。
 */
static void fc_InitLocalBuffers(void)
{
	int			fc_nbufs = num_temp_buffers;
	HASHCTL		fc_info;
	int			fc_i;

	/* 
	 * 并行工作线程无法访问临时表中的数据，因为它们
	 * 无法看到其领导者的本地缓冲区。这是提供便捷、低成本
	 * 检查的好地方。请注意，我们并不想阻止并行工作线程访问
	 * 关于临时表的目录元数据，因此在更高级别的检查是不合适的。
	 */
	if (IsParallelWorker())
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
				 errmsg("cannot access temporary tables during a parallel operation")));

	/* 分配并清零缓冲区头和辅助数组 */
	LocalBufferDescriptors = (BufferDesc *) calloc(fc_nbufs, sizeof(BufferDesc));
	LocalBufferBlockPointers = (Block *) calloc(fc_nbufs, sizeof(Block));
	LocalRefCount = (int32 *) calloc(fc_nbufs, sizeof(int32));
	if (!LocalBufferDescriptors || !LocalBufferBlockPointers || !LocalRefCount)
		ereport(FATAL,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));

	nextFreeLocalBuf = 0;

	/* 初始化需要从非零开始的字段 */
	for (fc_i = 0; fc_i < fc_nbufs; fc_i++)
	{
		BufferDesc *fc_buf = GetLocalBufferDescriptor(fc_i);

		/* 
		 * 负值表示本地缓冲区。这很棘手：共享缓冲区
		 * 从 0 开始。我们必须从 -2 开始。（注意，例程
		 * BufferDescriptorGetBuffer 将 buf_id 加 1，因此我们的第一个缓冲区 id
		 * 是 -1。）
		 */
		fc_buf->buf_id = -fc_i - 2;

		/* 
		 * 有意不初始化缓冲区的原子变量
		 * （除了上面的零化底层内存）。这样，如果有人（重新）引入
		 * 本地缓冲区的原子操作，就会在没有原子操作的平台上出现错误。
		 */
	}

	/* 创建查找哈希表 */
	fc_info.keysize = sizeof(BufferTag);
	fc_info.entrysize = sizeof(LocalBufferLookupEnt);

	LocalBufHash = hash_create("Local Buffer Lookup Table",
							   fc_nbufs,
							   &fc_info,
							   HASH_ELEM | HASH_BLOBS);

	if (!LocalBufHash)
		elog(ERROR, "could not initialize local buffer hash table");

	/* 初始化完成，标记已分配缓冲区 */
	NLocBuffer = fc_nbufs;
}

/* 
 * GetLocalBufferStorage - 为本地缓冲区分配内存
 *
 * 此函数的思想是聚合我们对存储的请求，
 * 以便内存管理器不会看到大量相对较小的请求。
 * 由于我们一旦在特定进程中创建本地缓冲区就不会将其归还，
 * 因此没有必要让 memmgr 负责分别管理的块。
 */
static Block
fc_GetLocalBufferStorage(void)
{
	static char *fc_cur_block = NULL;
	static int	fc_next_buf_in_block = 0;
	static int	fc_num_bufs_in_block = 0;
	static int	fc_total_bufs_allocated = 0;
	static MemoryContext fc_LocalBufferContext = NULL;

	char	   *fc_this_buf;

	Assert(fc_total_bufs_allocated < NLocBuffer);

	if (fc_next_buf_in_block >= fc_num_bufs_in_block)
	{
		/* 需要向 memmgr 发出新的请求 */
		int			fc_num_bufs;

		/* 
		 * 我们在自己的上下文中分配本地缓冲区，
		 * 以便在 MemoryContextStats 输出中轻松识别所占用的空间。
		 * 在第一次使用时创建该上下文。
		 */
		if (fc_LocalBufferContext == NULL)
			fc_LocalBufferContext =
				AllocSetContextCreate(TopMemoryContext,
									  "LocalBufferContext",
									  ALLOCSET_DEFAULT_SIZES);

		/* 从 16 个缓冲区请求开始；后续请求每次翻倍 */
		fc_num_bufs = Max(fc_num_bufs_in_block * 2, 16);
		/* 但不超过我们对所有剩余本地缓冲区的需求 */
		fc_num_bufs = Min(fc_num_bufs, NLocBuffer - fc_total_bufs_allocated);
		/* 也不要超过 MaxAllocSize。 */
		fc_num_bufs = Min(fc_num_bufs, MaxAllocSize / BLCKSZ);

		fc_cur_block = (char *) MemoryContextAlloc(fc_LocalBufferContext,
												fc_num_bufs * BLCKSZ);
		fc_next_buf_in_block = 0;
		fc_num_bufs_in_block = fc_num_bufs;
	}

	/* 在当前内存块中分配下一个缓冲区 */
	fc_this_buf = fc_cur_block + fc_next_buf_in_block * BLCKSZ;
	fc_next_buf_in_block++;
	fc_total_bufs_allocated++;

	return (Block) fc_this_buf;
}

/* 
 * CheckForLocalBufferLeaks - 确保该后端没有持有本地缓冲区的锁定
 *
 * 这就像 CheckForBufferLeaks()，但针对本地缓冲区。
 */
static void fc_CheckForLocalBufferLeaks(void)
{
#ifdef USE_ASSERT_CHECKING
	if (LocalRefCount)
	{
		int			fc_RefCountErrors = 0;
		int			fc_i;

		for (fc_i = 0; fc_i < NLocBuffer; fc_i++)
		{
			if (LocalRefCount[fc_i] != 0)
			{
				Buffer		fc_b = -fc_i - 1;

				PrintBufferLeakWarning(fc_b);
				fc_RefCountErrors++;
			}
		}
		Assert(fc_RefCountErrors == 0);
	}
#endif
}

/* 
 * AtEOXact_LocalBuffers - 在事务结束时清理。
 *
 * 这就像 AtEOXact_Buffers，但针对本地缓冲区。
 */
void AtEOXact_LocalBuffers(bool fc_isCommit)
{
	fc_CheckForLocalBufferLeaks();
}

/*
 * AtProcExit_LocalBuffers - 确保我们在后端退出时已释放固定。
 *
 * 这与 AtProcExit_Buffers 类似，但用于本地缓冲区。
 */
void AtProcExit_LocalBuffers(void)
{
	/*
	 * 我们不应该持有任何剩余的文件指针；如果我们持有，而且断言
	 * 未启用，我们将在 DropRelFileNodeBuffers 中失败，尝试删除临时关系时。
	 */
	fc_CheckForLocalBufferLeaks();
}
